//
//  CCBluetooth.m
//  CCBluetooth
//
//  Created by zjh on 2021/8/31.
//

#import "CCBluetooth.h"

NSString *const CCBluetoothPeripheralsChangeNotifyKey = @"CCBluetoothPeripheralsChangeNotifyKey";
NSString *const CCBluetoothPeripheralConnectedNotifyKey = @"CCBluetoothPeripheralConnectedNotifyKey";
NSString *const CCBluetoothPeripheralDisconnectedNotifyKey = @"CCBluetoothPeripheralDisconnectedNotifyKey";

@interface CCBluetooth()<CBCentralManagerDelegate, CBPeripheralDelegate, CCAuthProtocolDelegate> {
    
    NSMutableArray <CCPeripheral *> *__peripherals;
    
}

@property (nonatomic, strong) dispatch_semaphore_t lock;
@property (nonatomic, strong) NSHashTable *delegates;
@property (nonatomic, strong) CBCentralManager *centralManager;
@property (nonatomic, assign) CGFloat interval;//连接超时的时间
@property (atomic, assign) BOOL isScanning;
@property (nonatomic, assign) CCAuthorizationStatus _authorization; /// 权限

@end

static CCBluetooth *__central;
static dispatch_queue_t dispatch_queue;
static NSArray *__services;
static CGFloat __interval = 0;
static BOOL logEnabled = NO;

@implementation CCBluetooth

+ (instancetype)share {
    static dispatch_once_t oncetoken;
    dispatch_once(&oncetoken, ^{
        __central = [[CCBluetooth alloc] init];
    });
    return __central;
}

+ (void)setLogEnabled:(BOOL)bFlag {
    logEnabled = bFlag;
}

+ (instancetype)allocWithZone:(struct _NSZone *)zone {
    static dispatch_once_t oncetoken;
    dispatch_once(&oncetoken, ^{
        __central = [super allocWithZone:zone];
    });
    return __central;
}

+ (void)interval:(CGFloat)interval {
    __interval = interval;
}

+ (void)registerQueue:(dispatch_queue_t)queue {
    dispatch_queue = queue;
}

+ (void)registerServices:(NSArray <CBUUID *>*)services {
    __services = services;
}

- (id)copyWithZone:(NSZone *)zone {
    return __central;
}

- (id)init {
    if (self = [super init]) {
        [self setUp];
    }
    return self;
}

- (void)setUp {
    self.interval = 5;
    self.delegates = [NSHashTable weakObjectsHashTable];
#if  __IPHONE_OS_VERSION_MIN_REQUIRED > __IPHONE_6_0
        NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                                 [NSNumber numberWithBool:NO],CBCentralManagerOptionShowPowerAlertKey,
                                 nil];
#else
        NSDictionary *options = nil;
#endif
    NSArray *backgroundModes = [[[NSBundle mainBundle] infoDictionary]objectForKey:@"UIBackgroundModes"];
    if ([backgroundModes containsObject:@"bluetooth-central"]) {
        //后台模式
        self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:dispatch_queue options:options];
    }
    else {
        //非后台模式
        self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:dispatch_queue];
    }
    
    self.centralManager.delegate = self;
    self.lock = dispatch_semaphore_create(1);
    __peripherals = [[NSMutableArray alloc] init];
}

- (CCAuthorizationStatus)authorization {
    [self updateAuthorization];
    return __authorization;
}

- (void)updateAuthorization {
    if (@available(iOS 13.0, *)) {
        CBManagerAuthorization authStatus = [self.centralManager authorization];
        switch (authStatus) {
            case CBManagerAuthorizationDenied:
                __authorization = CCAuthorizationStatusDenied;
                break;
            case CBManagerAuthorizationRestricted:
                __authorization = CCAuthorizationStatusRestricted;
                break;
            case CBManagerAuthorizationAllowedAlways:
                __authorization = CCAuthorizationStatusAuthorized;
                break;
            case CBManagerAuthorizationNotDetermined:
                __authorization = CCAuthorizationStatusNotDetermined;
                break;
            default:
                break;
        }
    } else {
        switch (self.centralManager.state) {
            case CBManagerStatePoweredOff:
                [self removePeripherals:self.peripherals];
                __authorization = CCAuthorizationStatusSystemSetting;
                break;
            case CBManagerStateUnsupported:
                __authorization = CCAuthorizationStatusNotSupported;
                break;
            case CBManagerStatePoweredOn: {
                CBPeripheralManagerAuthorizationStatus authStatus = [CBPeripheralManager authorizationStatus];
                switch (authStatus) {
                    case CBPeripheralManagerAuthorizationStatusDenied:
                        __authorization = CCAuthorizationStatusDenied;
                        break;
                    case CBPeripheralManagerAuthorizationStatusAuthorized:
                        __authorization = CCAuthorizationStatusAuthorized;
                        break;
                    case CBPeripheralManagerAuthorizationStatusRestricted:
                        __authorization = CCAuthorizationStatusRestricted;
                        break;
                    case CBPeripheralManagerAuthorizationStatusNotDetermined:
                        __authorization = CCAuthorizationStatusNotDetermined;
                        break;
                    default:
                        break;
                }
                break;
            }
            case CBManagerStateUnknown:
                __authorization = CCAuthorizationStatusNotDetermined;
                break;
            case CBManagerStateUnauthorized:
                __authorization = CCAuthorizationStatusDenied;
                break;
            default:
                break;
        }
    }
}

- (NSArray *)peripherals {
    return __peripherals.copy;
}

- (CGFloat)interval {
    if (__interval > 0) {
        return __interval;
    }else{
        return _interval;
    }
}

- (void)setAuthProtocol:(id<CCAuthProtocol>)authProtocol {
    _authProtocol = authProtocol;
    _authProtocol.delegate = self;
}

- (void)setFilterProtocol:(id<CCFilterProtocol>)filterProtocol{
    _filterProtocol = filterProtocol;
}

- (void)startScanAndStopAfterDelay:(NSTimeInterval)delay {
    if (self.centralManager.state == CBManagerStatePoweredOn) {
        [self startScan];
        dispatch_async(dispatch_get_main_queue(), ^{
            [self performSelector:@selector(stopScan) withObject:self afterDelay:delay];
        });
    }
}

- (void)startScan {
    if (self.centralManager.state != CBManagerStatePoweredOn) {
        return;
    }
    dispatch_async(dispatch_get_main_queue(), ^{
        [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(stopScan) object:nil];
    });
    if (self.isScanning) {
#ifdef DEBUG
        if (logEnabled) {
            NSLog(@"正在扫描");
        }
#endif
        return;
    }
    self.isScanning = YES;
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"state != %d",CCPeripheralStateConnected];
    NSArray *willDeletePeripherals = [self->__peripherals.mutableCopy filteredArrayUsingPredicate:predicate];
    for (CCPeripheral *per in willDeletePeripherals) {
        if (per.state != CCPeripheralStateNone) {
            [self disconnect:per];
        }
    }
    [self removePeripherals:willDeletePeripherals];
    NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                                 [NSNumber numberWithBool:NO], CBCentralManagerScanOptionAllowDuplicatesKey,
                                 nil];
    [self.centralManager scanForPeripheralsWithServices:__services options:options];
    self.isScanning = self.centralManager.isScanning;
}

- (void)stopScan {
    dispatch_async(dispatch_get_main_queue(), ^{
        [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(stopScan) object:nil];
    });
    if (self.isScanning) {
        [self.centralManager stopScan];
        self.isScanning = NO;
    }
}

- (CCPeripheral *)getPeripheralForUUID:(NSString *)uuid{
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"uuid == %@",uuid];
    NSArray *existPeripherals = [self->__peripherals.mutableCopy filteredArrayUsingPredicate:predicate];
    if (existPeripherals.count) {
        return existPeripherals.firstObject;
    }
    return nil;
}

- (void)addPeripheral:(CCPeripheral *)peripheral{
    dispatch_semaphore_wait(self.lock, DISPATCH_TIME_FOREVER);
    [self->__peripherals addObject:peripheral];
    dispatch_semaphore_signal(self.lock);
    [[NSNotificationCenter defaultCenter] postNotificationName:CCBluetoothPeripheralsChangeNotifyKey object:nil];
}

- (void)removePeripherals:(NSArray *)peripherals{
    dispatch_semaphore_wait(self.lock, DISPATCH_TIME_FOREVER);
    [self->__peripherals removeObjectsInArray:peripherals];
    dispatch_semaphore_signal(self.lock);
    [[NSNotificationCenter defaultCenter] postNotificationName:CCBluetoothPeripheralsChangeNotifyKey object:nil];
}

- (void)updatePeripheral:(CCPeripheral *)peripheral{//值更新名字和设备状态
    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"uuid == %@",peripheral.uuid];
    NSArray *existPeripherals = [self->__peripherals.mutableCopy filteredArrayUsingPredicate:predicate];
    if (existPeripherals.count) {
        dispatch_semaphore_wait(self.lock, DISPATCH_TIME_FOREVER);
        for (CCPeripheral *per in existPeripherals) {
            per.name = peripheral.name;
            per.state = peripheral.state;
        }
        dispatch_semaphore_signal(self.lock);
        [[NSNotificationCenter defaultCenter] postNotificationName:CCBluetoothPeripheralsChangeNotifyKey object:nil];
    }else{
        [self addPeripheral:peripheral];
    }
}
- (void)connect:(CCPeripheral *)peripheral {
#ifdef DEBUG
    if (logEnabled) {
        NSLog(@"%@--连接设备---%@",peripheral.uuid,peripheral);
    }
#endif
    [self performTimeOut:peripheral];
    peripheral.state = CCPeripheralStateConnectting;
    [self updatePeripheral:peripheral];
    [self.centralManager connectPeripheral:peripheral.peripheral options:nil];
}

- (void)disconnect:(CCPeripheral *)peripheral {
    peripheral.state = CCPeripheralStateNone;
    [self updatePeripheral:peripheral];
    [self.centralManager cancelPeripheralConnection:peripheral.peripheral];
}

- (void)disconnectAll {
    for (CCPeripheral *p in self.peripherals) {
        if (p.state == CCPeripheralStateConnected) {
            p.state = CCPeripheralStateNone;
            [self updatePeripheral:p];
            [self.centralManager cancelPeripheralConnection:p.peripheral];
        }
    }
}

- (void)centralManagerDidUpdateState:(CBCentralManager *)central{
    [self updateAuthorization];
    switch (central.state) {
        case CBManagerStatePoweredOff:{
            NSArray *peripherals = self.peripherals;
            [self removePeripherals:self.peripherals];
            for (CCPeripheral *peripheral in peripherals) {
                [[NSNotificationCenter defaultCenter] postNotificationName:CCBluetoothPeripheralDisconnectedNotifyKey object:peripheral];
            }
        }break;
        default:
            break;
    }
    NSArray *delegates = [self.delegates allObjects];
    for (id<CBCentralManagerDelegate> obj in delegates) {
        if ([obj respondsToSelector:@selector(centralManagerDidUpdateState:)]) {
            [obj centralManagerDidUpdateState:central];
        }
    }
}

- (void)addDelegate:(id)delegate{
    if (![self.delegates containsObject:delegate]) {
        [self.delegates addObject:delegate];
    }
}

- (void)performTimeOut:(CCPeripheral *)peripheral {
    dispatch_async(dispatch_get_main_queue(), ^{
        [self performSelector:@selector(connectTimeOut:) withObject:peripheral afterDelay:self.interval];
    });
}
- (void)cancelPerformTimeOut:(CCPeripheral *)peripheral {
    dispatch_async(dispatch_get_main_queue(), ^{
        [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(connectTimeOut:) object:peripheral];
    });
}

- (void)removeDelegate:(id)delegate{
    [self.delegates removeObject:delegate];
}
- (void)connectTimeOut:(CCPeripheral *)peripheral {
#ifdef DEBUG
    if (logEnabled) {
        NSLog(@"连接超时：%@",peripheral.uuid);
    }
#endif
    if (peripheral.state == CCPeripheralStateConnectting) {
        [self disconnect:peripheral];
    }
}

- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary<NSString *,id> *)advertisementData RSSI:(NSNumber *)RSSI {
#ifdef DEBUG
    if (logEnabled) {
        NSLog(@"搜索到设备：%@",peripheral.identifier.UUIDString);
    }
#endif
    CCPeripheral *per = [[CCPeripheral alloc] initWithPeripheral:peripheral];
    if ([advertisementData.allKeys containsObject:@"kCBAdvDataLocalName"]) {
        per.name = [advertisementData objectForKey:@"kCBAdvDataLocalName"];
    }
    if ([advertisementData.allKeys containsObject:@"kCBAdvDataManufacturerData"]) {
        NSData *data = advertisementData[@"kCBAdvDataManufacturerData"];
        NSString *mac = [self.class convertDataToHexStr:data];
        per.mac = mac;
    }
    if ([self.filterProtocol respondsToSelector:@selector(filterDiscover:)]) {
        if ([self.filterProtocol filterDiscover:per]) {
            NSPredicate *predicate = [NSPredicate predicateWithFormat:@"uuid == %@",per.uuid];
            NSArray *existPeripherals = [self->__peripherals.mutableCopy filteredArrayUsingPredicate:predicate];
            [self removePeripherals:existPeripherals];
            [self addPeripheral:per];
        };
    }else{
        NSPredicate *predicate = [NSPredicate predicateWithFormat:@"uuid == %@",per.uuid];
        NSArray *existPeripherals = [self->__peripherals.mutableCopy filteredArrayUsingPredicate:predicate];
        [self removePeripherals:existPeripherals];
        [self addPeripheral:per];
    }
    if ([self.filterProtocol respondsToSelector:@selector(filterAutoConnect:)]) {
        if ([self.filterProtocol filterAutoConnect:per]) {
            [self connect:per];
        }
    }
    NSArray *delegates = [self.delegates allObjects];
    for (id<CBCentralManagerDelegate> obj in delegates) {
        if ([obj respondsToSelector:@selector(centralManager:didDiscoverPeripheral:advertisementData:RSSI:)]) {
            [obj centralManager:central didDiscoverPeripheral:peripheral advertisementData:advertisementData RSSI:RSSI];
        }
    }
}

- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral {
    CCPeripheral *per = [self getPeripheralForUUID:peripheral.identifier.UUIDString];
    if (!per) {
        per = [[CCPeripheral alloc] initWithPeripheral:peripheral];
        [self addPeripheral:per];
    }
    if (!self.authProtocol) {//不支持认证协议，连接成功即已连接
        [self cancelPerformTimeOut:per];
        per.state = CCPeripheralStateConnected;
        [self updatePeripheral:per];
        [[NSNotificationCenter defaultCenter] postNotificationName:CCBluetoothPeripheralConnectedNotifyKey object:peripheral];
    }
    per.peripheral.delegate = self;
    [per.peripheral discoverServices:nil];
    NSArray *delegates = [self.delegates allObjects];
    for (id<CBCentralManagerDelegate> obj in delegates) {
        if ([obj respondsToSelector:@selector(centralManager:didConnectPeripheral:)]) {
            [obj centralManager:central didConnectPeripheral:peripheral];
        }
    }
}
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
    CCPeripheral *per = [self getPeripheralForUUID:peripheral.identifier.UUIDString];
    if (per) {
        [self cancelPerformTimeOut:per];
    }
    if (error) {
#ifdef DEBUG
        if (logEnabled) {
            NSLog(@"连接失败：%@",error);
        }
#endif
    }
}
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error {
    CCPeripheral *per = [self getPeripheralForUUID:peripheral.identifier.UUIDString];
    if (!per) {
        return;
    }
#ifdef DEBUG
    if (logEnabled) {
        NSLog(@"断开连接：%@",per.uuid);
    }
#endif
    [per clearListen];
    per.peripheral = peripheral;
    per.state = CCPeripheralStateNone;
    [self updatePeripheral:per];
    [[NSNotificationCenter defaultCenter] postNotificationName:CCBluetoothPeripheralDisconnectedNotifyKey object:per];
    NSArray *delegates = [self.delegates allObjects];
    for (id<CBCentralManagerDelegate> obj in delegates) {
        if ([obj respondsToSelector:@selector(centralManager:didDisconnectPeripheral:error:)]) {
            [obj centralManager:central didDisconnectPeripheral:peripheral error:error];
        }
    }
}

- (void)peripheral:(CBPeripheral *)peripheral didDiscoverServices:(NSError *)error {
    CCPeripheral *per = [self getPeripheralForUUID:peripheral.identifier.UUIDString];
    if (!per) {
        return;
    }
    [per.services removeAllObjects];
    [per.services addObjectsFromArray:peripheral.services];
    for (CBService *service in peripheral.services) {
        [per.peripheral discoverCharacteristics:nil forService:service];
    }
    NSArray *delegates = [self.delegates allObjects];
    for (id<CBPeripheralDelegate> obj in delegates) {
        if ([obj respondsToSelector:@selector(peripheral:didDiscoverServices:)]) {
            [obj peripheral:peripheral didDiscoverServices:error];
        }
    }
}

- (void)peripheral:(CBPeripheral *)peripheral didDiscoverCharacteristicsForService:(nonnull CBService *)service error:(nullable NSError *)error {
    CCPeripheral *per = [self getPeripheralForUUID:peripheral.identifier.UUIDString];
    if (!per) {
        return;
    }
    [per.services removeObject:service];
    if (per.services.count == 0) {
        if ([self.authProtocol respondsToSelector:@selector(start:)]) {
            [self.authProtocol start:per];
        }
        NSArray *delegates = [self.delegates allObjects];
        for (id<CBPeripheralDelegate> obj in delegates) {
            if ([obj respondsToSelector:@selector(peripheral:didDiscoverCharacteristicsForService:error:)]) {
                [obj peripheral:peripheral didDiscoverCharacteristicsForService:service error:error];
            }
        }
    }
}

//- (void)peripheral:(CBPeripheral *)peripheral didWriteValueForCharacteristic:(nonnull CBCharacteristic *)characteristic error:(nullable NSError *)error{
//    if (error) {
//        NSLog(@"数据写入错误:%@",error);
//    }else{
//        NSLog(@"数据写入成功");
//
//    }
//}
//- (void)peripheral:(CBPeripheral *)peripheral didUpdateNotificationStateForCharacteristic:(CBCharacteristic *)characteristic error:(NSError *)error {
//    if (error) {
//        NSLog(@"设置通知错误:%@",error);
//    }else{
//        NSLog(@"设置通知成功");
//    }
//}

- (void)peripheral:(CBPeripheral *)peripheral didUpdateValueForCharacteristic:(nonnull CBCharacteristic *)characteristic error:(nullable NSError *)error {
#ifdef DEBUG
    if (logEnabled) {
        NSLog(@"设备回复数据：%@",characteristic.value);
    }
#endif
    CCPeripheral *per = [self getPeripheralForUUID:peripheral.identifier.UUIDString];
    if (per) {
        if (self.authProtocol) {
            if ([self.authProtocol respondsToSelector:@selector(receive:characteristic:)]) {
                [self.authProtocol receive:per characteristic:characteristic];
            }
        }
        if ([per.listenCharacteristices containsObject:characteristic.UUID.UUIDString]) {
            NSArray *delegates = [self.delegates allObjects];
            for (id<CBPeripheralDelegate> obj in delegates) {
                if ([obj respondsToSelector:@selector(peripheral:didUpdateValueForCharacteristic:error:)]) {
                    [obj peripheral:peripheral didUpdateValueForCharacteristic:characteristic error:error];
                }
            }
        }
    }
}

#pragma mark CCAuthProtocolDelegate
- (void)authResult:(CCPeripheral *)peripheral success:(BOOL)success {
    if (success) {//支持认证协议，认证通过即已连接
#ifdef DEBUG
        if (logEnabled) {
            NSLog(@"%@---认证成功---%@",peripheral.uuid,peripheral);
        }
#endif
        [self cancelPerformTimeOut:peripheral];
        peripheral.state = CCPeripheralStateConnected;
        [self updatePeripheral:peripheral];
        [[NSNotificationCenter defaultCenter] postNotificationName:CCBluetoothPeripheralConnectedNotifyKey object:peripheral];
    }
}

+ (NSString *)convertDataToHexStr:(NSData *)data {
    if (!data || [data length] == 0) {
        return @"";
    }
    NSMutableString *string = [[NSMutableString alloc] initWithCapacity:[data length]];
    
    [data enumerateByteRangesUsingBlock:^(const void *bytes, NSRange byteRange, BOOL *stop) {
        unsigned char *dataBytes = (unsigned char*)bytes;
        for (NSInteger i = 0; i < byteRange.length; i++) {
            NSString *hexStr = [NSString stringWithFormat:@"%x", (dataBytes[i]) & 0xff];
            if ([hexStr length] == 2) {
                [string appendString:hexStr];
            } else {
                [string appendFormat:@"0%@", hexStr];
            }
        }
    }];
    
    return string;
}

@end
